#include "console-common.h"
#include <cstdlib> // malloc, free, NULL
#include <cstdio> // fopen, fclose, fread, fwrite
#include <cstring> // memcpy
#include <string> // std::string
#include <ctime> // struct tm
#include "lua-conwin.h"
#include "lua-conwin-altmode.h"
#include "lua-extra-attr.h"
#include <windows.h>
#include <SDL/SDL.h>
#include <algorithm> // std::min, std::max

#define altf luaevent_altkey_flags

using namespace std;

int scrn_prev_freed = 1;
int scrn_window_count = 1;
int luacon_altmenu_suspend_count = 0;

char* SDL_curr_status = NULL;

int luacon_window_cnt (lua_State * l)
{
	lua_pushinteger(l, scrn_window_count);
	return 1;
}

int luacon_window_cpct (lua_State * l)
{
	lua_pushinteger(l, semigraphics_bufs.size());
	return 1;
}

int luacon_altmenu_suspend (lua_State * l)
{
	EnterCriticalSection(&luacon_critical_0);
	lua_pushinteger(l, luacon_altmenu_suspend_count);
	luacon_altmenu_suspend_count ++;
	LeaveCriticalSection(&luacon_critical_0);
	return 1;
}

int luacon_altmenu_resume (lua_State * l)
{
	EnterCriticalSection(&luacon_critical_0);
	lua_pushinteger(l, luacon_altmenu_suspend_count);
	if (luacon_altmenu_suspend_count)
		luacon_altmenu_suspend_count --;
	LeaveCriticalSection(&luacon_critical_0);
	return 1;
}

int luacon_window_recalc (lua_State * l)
{
	int	lastActiveIndex = -1, lastUnused = -1,
		scrn_window_count = 0, i;

	int size = semigraphics_bufs.size();
	for (i = 0; i < size; i++)
	{
		if (IS_FREED_BUFF_BOUNDED(i, semigraphics_bufs))
		{
			if (lastUnused < 0) scrn_prev_freed = i;
			else semigraphics_bufs[lastUnused] = (int32_t*)i;
			lastUnused = i;
		}
		else
		{
			lastActiveIndex = i;
			scrn_window_count++;
		}
	}
	if (lastUnused < 0)
		scrn_prev_freed = lastActiveIndex + 1;
	else
		semigraphics_bufs[lastUnused] = (int32_t*)(lastActiveIndex + 1);

	semigraphics_bufs.resize(lastActiveIndex + 1);
	return 0;
}

int console_alloc_scrn (int i)
{
	int size;
	bool freed;
	int32_t* tmp_buf;

	if (scrn_prev_freed >= MAX_FREED_INDEX) return -1;

	tmp_buf = (int32_t*)malloc(XCHRS*YCHRS*SSKIP);
	if (tmp_buf == NULL) return -1;

	freed = IS_FREED_BUFF(i, semigraphics_bufs);

	if (!freed)
		memcpy(tmp_buf, semigraphics_bufs[i], XCHRS*YCHRS*SSKIP);
	else
		memset(tmp_buf, 0, XCHRS*YCHRS*SSKIP);

	size = semigraphics_bufs.size();
	if (scrn_prev_freed >= size)
		semigraphics_bufs.push_back((int32_t*)(size + 1));

	// i = f, f = t[i]
	i = scrn_prev_freed;
	scrn_prev_freed = (int)semigraphics_bufs[i];
	semigraphics_bufs[i] = tmp_buf;
	scrn_window_count++;

	return i;
}

int luacon_alloc_scrn (lua_State * l)
{
	int i = luaL_optint(l, 1, gfx_index);
	i = console_alloc_scrn(i);
	lua_pushinteger(l, i);
	return 1;
}

bool console_free_scrn (int i)
{
	if (i != 0 && !IS_FREED_BUFF(i, semigraphics_bufs))
	{
		free(semigraphics_bufs[i]);
		if (i == gfx_index)
			gfx_index = 0;

		// t[i] = f, f = i
		semigraphics_bufs[i] = (int32_t*)scrn_prev_freed;
		scrn_prev_freed = i;
		scrn_window_count--;
		return true;
	}
	return false;
}

int luacon_free_scrn (lua_State * l)
{
	int i = luaL_optint(l, 1, gfx_index);
	bool ret_status = console_free_scrn(i);
	lua_pushboolean(l, ret_status);
	return 1;
}

#if XCHRS >= 25
#define fmt "| %4d/%2d/%2d %2d:%02d:%02d"
#else
#define fmt "%4d/%2d/%2d %2d:%02d:%02d"
#endif

static void __drawstr (int32_t *buf, int x, int y, const char *str, int len)
{
	int i;
	for (i = 0; i < len && str[i]; i++)
		drawchr(buf, x + i, y, str[i] & 0xFF);
	if (str[i] != '\0')
		for (i = max(len - 3, 1); i < len; i++)
			drawchr(buf, x + i, y, '.');
}

void console_blue_screen (lua_State * L, int32_t *buf)
{
	curr_fg = 0xFFFFFF, curr_bg = 0x80;
	clear_line(buf, 0, YCHRS);
	free(sdl_blinking_flags_0);
	sdl_blinking_flags_curr = NULL;
	sdl_blinking_flags_0 = NULL;
	
	__drawstr (buf, 0, 0, "UNPROTECTED ERROR", XRES);
#if (XCHRS >= 13)
	__drawstr (buf, 0, 1, "Details:", 8);
	__drawstr (buf, 9, 1, lua_tostring(L, -1), (XCHRS - 9));
#endif
}

#define DISABLE_BLINKING_LINE_NOCHECK(a, b) { for (int _i = (a) * XCHRS; _i < ((a) + (b)) * XCHRS; _i++) sdl_blinking_flags_1[_i] = 0; }
#define DISABLE_BLINKING_LINE(a, b) if (sdl_blinking_flags_1 != NULL) DISABLE_BLINKING_LINE_NOCHECK(a, b)

int SDL_add_status_bar (int32_t *buf)
{
#if (XCHRS >= 21) && (YRES >= 4)
	curr_bg = 0xFFFFFF;
	curr_fg = 0x000000;
	clear_line(buf, ALT_STATUS_LINE);
	
	DISABLE_BLINKING_LINE(ALT_STATUS_LINE, 1)

	struct tm *ptr;
	time_t lt;
	lt = time(NULL);
	ptr = localtime(&lt);
	char buf_time[25];
	int len_tm;
	sprintf(buf_time, fmt, ptr->tm_year+1900, ptr->tm_mon+1, ptr->tm_mday, ptr->tm_hour, ptr->tm_min, ptr->tm_sec);
	len_tm = strlen(buf_time);
	__drawstr(buf, XCHRS - (len_tm + 1), ALT_STATUS_LINE, buf_time, len_tm);
#if XCHRS >= 25
	if (altf & ALT_ERR_RUN1)
		curr_fg = 0xFF0000;
	__drawstr(buf, 1, ALT_STATUS_LINE, SDL_curr_status, XCHRS - len_tm - 3);
#endif
	if (altf & (ALT_MSG_PUSH1 | ALT_MSG_PUSH2))
	{
		if (altf & ALT_MSG_PUSH1)
			curr_fg = 0x800080;
		else
			curr_fg = 0xFF0000, curr_bg = 0xCCCC00;
		drawchr(buf, 0, ALT_STATUS_LINE, '+');
	}
#endif
	return 0;
}

int luacon_switch_window (lua_State * l)
{
	int args = lua_gettop(l);
	if (args < 1)
	{
		lua_pushinteger(l, gfx_index);
		return 1;
	}
	int w = luaL_checkint(l, 1);

	if (!IS_FREED_BUFF(w, semigraphics_bufs))
		gfx_index = w;
	else
		gfx_index = 0;

	return 0;
}

int luacon_check_window (lua_State * l)
{
	int w = luaL_checkint(l, 1);
	lua_pushboolean(l, !IS_FREED_BUFF(w, semigraphics_bufs));
	return 1;
}

int luacon_next_window (lua_State * L)
{
	int inc = lua_toboolean(L, 1) ? -1 : 1, p = gfx_index, sz = semigraphics_bufs.size();
	do p += inc, (p >= sz) && (p = 0), (p < 0) && (p = sz - 1);
	while (IS_FREED_BUFF(p, semigraphics_bufs));
	lua_pushinteger(L, gfx_index = p);
	return 1;
}

int luacon_window_fileop (lua_State * l)
{
	int page;

	string filename = "Saves\\";
	filename += luaL_checkstring(l, 1);
	bool w = lua_toboolean(l, 2);
	page = luaL_optint_gfx_i(l, 3);
	FILE * fhandle = fopen(filename.c_str(), (w ? "wb" : "rb"));
	if (w)
	{
		CreateDirectory("Saves", NULL);
		fwrite(semigraphics_bufs[page], XCHRS*YCHRS*SSKIP, 1, fhandle);
	}
	else
		fread(semigraphics_bufs[page], XCHRS*YCHRS*SSKIP, 1, fhandle);
	fclose(fhandle);
	return 0;
}

int luacon_set_fullscreen (lua_State * l)
{
	EnterCriticalSection(&luacon_critical_0);
	int args = lua_gettop(l);
	if (args >= 1)
		fullscreened = lua_toboolean(l, 1);
	if (args >= 2)
		enable_fullscreen_shortcut = lua_toboolean(l, 2);
	fullscreen_wait = true;
	LeaveCriticalSection(&luacon_critical_0);
	return 0;
}

#define ROW_TO_OFFSET(x) (x*XCHRS*(SSKIP/4))

int luacon_alt_exec_fnw (lua_State * L)
{
	int cmd = luaL_checkint(L, 1);
	int i = luaL_checkint(L, 2), j, retn = 0;
	bool getting = false;

	const int32_t* src_buf;
	int32_t* dst_buf;

	switch (cmd & (ALT_WIN_CMD_ALLOC | ALT_WIN_CMD_FREE | ALT_WIN_CMD_GET))
	{
	case ALT_WIN_CMD_ALLOC:
		retn = i = console_alloc_scrn(i);
		break;
	case ALT_WIN_CMD_FREE:
		if (gfx_index != i)
		{
			retn = console_free_scrn(i);
			if (tmp_gfx_index_2 == i) tmp_gfx_index_2 = 0;
		}
		break;
	case ALT_WIN_CMD_GET:
		if (cmd == ALT_WIN_CMD_CHECK)
		{
			retn = !IS_FREED_BUFF_BOUNDED(i, semigraphics_bufs);
			goto get0;
		}
		else
		{
			int s[3] = { tmp_gfx_index, tmp_gfx_index_2, gfx_index };
			if (i < 0) i = -i;
			if (i < 0 || i > 2) i = 0;
			retn = s[i], i = retn, getting = true;
			goto view0;
		}
	}

	if (cmd & ALT_WIN_CMD_JUMP)
	{
		if (i >= 0 && i < (int)semigraphics_bufs.size())
			tmp_gfx_index = i, retn = 1;
		else
			retn = 0;
	}
view0:
	if (cmd & ALT_WIN_CMD_VIEW)
	{
		bool cansetmem = false;
		if (IS_FREED_BUFF_BOUNDED(i, semigraphics_bufs))
			(retn = 0), (i < 0) && (cansetmem = true);
		else
		{
			src_buf = semigraphics_bufs[i];
			dst_buf = semigraphics_bufs[gfx_index];
			getting || (retn = 1);
			cansetmem = true;
		}
		if (cansetmem)
			for (j = 3; j < YCHRS; j++)
			{
				if (j == ALT_STATUS_LINE)
					continue;
				if (i < 0)
					memset(dst_buf+ROW_TO_OFFSET(j), 0, XCHRS*SSKIP);
				else
					memcpy(dst_buf+ROW_TO_OFFSET(j), src_buf+ROW_TO_OFFSET(j), XCHRS*SSKIP);
			}
	}

	recreate_window_menu(false);
get0:
	lua_pushinteger(L, retn);
	return 1;
}

#define DRAW_CHAR_HIGHLIGHT(i, chr, col)\
	(* (char*) & buf [beginoffset+(SSKIP/4)*i] = chr,\
	buf [beginoffset+(SSKIP/4)*i+1] = col)
#define DRAW_CHAR_BOUND(i, chr, col)\
	if (i >= 0 && i < XCHRS) DRAW_CHAR_HIGHLIGHT(i, chr, col);

void recreate_window_menu (bool init)
{
	if (gfx_index < 0)
	{
		altf &= ~ALT_ENABLED;
		console_altmode_call (true, NULL);	// because gfx_index < 0 causes "do nothing"
		return;
	}

	int size = semigraphics_bufs.size(), i, j, k;
	static int begin = 0;
	if (init)
		begin = tmp_gfx_index - (XCHRS / 2);
	if (begin + XCHRS > size) begin = size - XCHRS;
	if (begin < 0) begin = 0;

	curr_bg = 0x000000;
	curr_fg = 0xFFFFFF;

	int32_t *buf = semigraphics_bufs[gfx_index];
	clear_line(buf, 0, 3);
	
	if (sdl_blinking_flags_1 != NULL)
	{
		memcpy(sdl_blinking_flags_1, sdl_blinking_flags_0, XCHRS * YCHRS * sizeof(BLINK_FLAGS_TYPE));
		DISABLE_BLINKING_LINE_NOCHECK(0, 3)

		sdl_blinking_flags_curr = sdl_blinking_flags_1;
	}

	int beginoffset = ROW_TO_OFFSET(2);
	for (i = 0; i < XCHRS*(SSKIP/4); i += (SSKIP/4))
		* (char*) & buf [beginoffset+i] = '=',
		buf [beginoffset+i+1] = curr_fg;
		
	beginoffset = ROW_TO_OFFSET(0);
	for (i = 0; i < size; i++)
	{
		* (char*) & buf [beginoffset+(SSKIP/4)*i] = IS_FREED_BUFF_BOUNDED(begin + i, semigraphics_bufs) ? '-' : 'W';
		buf [beginoffset+(SSKIP/4)*i+1] = curr_fg; 
	}
	beginoffset = ROW_TO_OFFSET(1);
	
	i = tmp_gfx_index - begin;
	j = gfx_index - begin;
	k = tmp_gfx_index_2 - begin;

	DRAW_CHAR_BOUND (j, '^', 0xFF0000);
	DRAW_CHAR_BOUND (k, '^', 0x555555);
	DRAW_CHAR_HIGHLIGHT (i, '^', 0x00FF00);
}